﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using CoreEx.Common.Extensions;
using CoreEx.Common.Proxy;
using CoreEx.Database.Client;
using CoreEx.DynamicInvoker;
using LinFu.AOP.Interfaces;
using LinFu.IoC.Configuration;
using LinFu.IoC;
using LinFu.IoC.Interfaces;
using System.Data.Common;

namespace CoreEx.Database.Client.Implementation
{
    [Implements(typeof(IInvokeWrapper<IDbCommand>), LifecycleType.OncePerRequest)]
    public class CommandInvokeWrapper : InvokeWrapperBase<IDbCommand>
    {
        private IDbConnection _proxyConnection;
        private IDbTransaction _proxyTransaction;
        private bool _implicitConnection;
        

        public CommandInvokeWrapper(IDbCommand actualTarget)
            : base(actualTarget)
        {
        }

        public override void BeforeInvoke(IInvocationInfo info)
        {
            if (info.TargetMethod.Name == "ExecuteReader" 
                || info.TargetMethod.Name == "ExecuteNonQuery"
                || info.TargetMethod.Name == "ExecuteScalar")
            {
                OpenConnection(((IDbCommand)info.Target).Connection);
            }

            base.BeforeInvoke(info);
        }

        private void OpenConnection(IDbConnection connection)
        {
            if (connection.State != ConnectionState.Open)
            {
                connection.Open();
                _implicitConnection = true;
            }
        }

        private void CloseConnection(IDbConnection connection, bool isReader)
        {                        
  
            //If we opened the connection we are also permitted to close it
            if (!_implicitConnection && !isReader)
                return; 
            
            connection.Close();
        }



        public override object DoInvoke(IInvocationInfo info)
        {
            if (info.TargetMethod.Name == "get_Connection")
            {
                return _proxyConnection;
            }
            if (info.TargetMethod.Name == "set_Connection")
            {                               
                _proxyConnection = (IDbConnection)info.Arguments[0];
                var actualConnection = ((IActualTarget)info.Arguments[0]).Instance;
                return info.TargetMethod.DynamicInvoke(ActualTarget, new []{actualConnection});                
                
            }

            if (info.TargetMethod.Name == "get_Transaction")
            {
                return _proxyTransaction;
            }
            if (info.TargetMethod.Name == "set_Transaction")
            {              
                _proxyTransaction = (IDbTransaction) info.Arguments[0];
                if (_proxyTransaction == null)
                    info.TargetMethod.DynamicInvoke(ActualTarget, new[] { _proxyTransaction });
                else
                {
                    var actualTransaction = ((IActualTarget) info.Arguments[0]).Instance;
                    return info.TargetMethod.DynamicInvoke(ActualTarget, new[] {actualTransaction});
                }

            }

            return base.DoInvoke(info);
        }

        public override void AfterInvoke(IInvocationInfo info, object returnValue)
        {
            if (info.TargetMethod.Name == "set_CommandText"
                || info.TargetMethod.Name == "set_CommandType"
                || info.TargetMethod.Name == "set_Connection")
            {
                if (ActualTarget.Connection != null && ActualTarget.CommandType == CommandType.StoredProcedure)
                {
                    ActualTarget.Parameters.Clear();
                    ServiceContainer.GetService<IParameterCache>(((IDbConnectionInfo)((IDbCommand)info.Target).Connection).Name)
                        .GetParameters(ActualTarget.CommandText).ForEach(p => ActualTarget.Parameters.Add(p));
                }
            }

            if ( info.TargetMethod.Name == "ExecuteNonQuery"
                || info.TargetMethod.Name == "ExecuteScalar")
            {
                CloseConnection(((IDbCommand)info.Target).Connection, info.TargetMethod.Name == "ExecuteReader");
            }

            base.AfterInvoke(info, returnValue);
        }

        public override void Initialize(IServiceContainer source)
        {
            base.Initialize(source);            
        }

    }
}
